package main

import (
	"fmt"
	"time"
)

/*

select 语义是和 channel 绑定在一起使用的，select 可以实现从多个 channel 收发数据，可以使得一个 goroutine 就可以处理多个 channel 的通信。

语法上和 switch 类似，有 case 分支和 default 分支，只不过 select 的每个 case 后面跟的是 channel 的收发操作。

语法上和switch的一些区别：

select 关键字和后面的 { 之间，不能有表达式或者语句。
每个 case 关键字后面跟的必须是 channel 的发送或者接收操作
允许多个 case 分支使用相同的 channel，case 分支后的语句甚至可以重复
*/

var ch1 = make(chan int)
var ch2 = make(chan int)
var ch3 = make(chan int)

func main() {

	go func() {
		for i := 0; i < 10; i++ {
			ch1 <- i
			ch2 <- i
			ch3 <- i
		}
		close(ch1)
		close(ch2)
		close(ch3)
	}()

	noBlockSelect()

	blockAndReceive()
}

// noBlockSelect 函数演示了如何在 Go 语言中使用 select 语句实现非阻塞的 channel 操作
// 它通过设置一个超时时间来避免无限等待，确保程序在没有接收到数据时能够及时响应并处理超时情况
func noBlockSelect() {
	// 创建一个超时 channel，用于在 select 语句中设置最大等待时间
	// 在这个例子中，如果2秒内没有接收到数据，则认为操作超时
	timeout := time.After(2 * time.Second)
	for {
		select {
		// 尝试从 ch1 读取数据，如果成功则打印接收到的数据
		// 如果 ch1 被关闭且没有数据可读，则打印消息并退出函数
		case v, ok := <-ch1:
			if !ok {
				fmt.Println("Channel已关闭")
				// return 会立即终止 noBlockSelect 函数的执行。
				return
			}
			fmt.Printf("主goroutine接收到: %d\n", v)
		// 如果超过设定的超时时间，没有 case 可以执行，则执行超时处理逻辑
		case <-timeout:
			fmt.Println("操作超时")
			return
		// default case 确保 select 语句不会阻塞，如果没有数据可读，则打印消息并稍作等待
		default:
			fmt.Println("没有数据，等待中...")
			time.Sleep(500 * time.Millisecond)
		}
	}
}

// 阻塞并等待数据
func blockAndReceive() {
	for i := 0; i < 30; i++ {
		select {
		/*
			<-ch1：尝试从 channel ch1 接收数据。
			v := <-ch1：如果 ch1 有数据可接收，则将接收到的值赋给变量
		*/
		case v := <-ch1:
			fmt.Println("ch1:", v)
		case v := <-ch2:
			fmt.Println("ch2:", v)
		case v := <-ch3:
			fmt.Println("ch3:", v)
		}
	}
}
